pub mod pkce;
mod jwks;

use crate::config;
use crate::oidc::jwks::JwkService;
use crate::support::error::{AppError, AppResult};
use anyhow::anyhow;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use url::Url;

// -- const --
pub const USER_SESSION_KEY: &str = "RidUserSession";
pub const OIDC_AUTH_FLOW_STATE_KEY: &str = "OidcAuthFlowState";

pub const RESPONSE_TYPE: &str = "response_type";
pub const SCOPE: &str = "scope";
pub const OPENID: &str = "openid";
pub const CODE: &str = "code";
pub const CLIENT_ID: &str = "client_id";
pub const CLIENT_SECRET: &str = "client_secret";
pub const CODE_VERIFIER: &str = "code_verifier";
pub const NONCE: &str = "nonce";
pub const STATE: &str = "state";
pub const CODE_CHALLENGE: &str = "code_challenge";
pub const CODE_CHALLENGE_METHOD: &str = "code_challenge_method";
pub const REDIRECT_URI: &str = "redirect_uri";
pub const GRANT_TYPE: &str = "grant_type";
pub const AUTHORIZATION_CODE: &str = "authorization_code";


// -- OIDC Provider Metadata--
#[derive(Debug, Deserialize, Clone)]
pub struct OidcProviderMetadata {
    issuer_uri: String,
    authorization_endpoint: String,
    token_endpoint: String,
    jwks_uri: String,
    userinfo_endpoint: Option<String>,
}

impl OidcProviderMetadata {
    pub fn from_discovery_doc(doc: serde_json::Value) -> AppResult<Self> {
        Ok(
            OidcProviderMetadata {
                issuer_uri: doc["issuer"].as_str()
                    .ok_or_else(|| AppError::InternalError(anyhow!("issuer not found in discovery document")))?
                    .to_string(),
                authorization_endpoint: doc["authorization_endpoint"].as_str()
                    .ok_or_else(|| AppError::InternalError(anyhow!("authorization_endpoint not found in discovery document")))?
                    .to_string(),
                token_endpoint: doc["token_endpoint"].as_str()
                    .ok_or_else(|| AppError::InternalError(anyhow!("token_endpoint not found in discovery document")))?
                    .to_string(),
                jwks_uri: doc["jwks_uri"].as_str()
                    .ok_or_else(|| AppError::InternalError(anyhow!("jwks_uri not found in discovery document")))?
                    .to_string(),
                userinfo_endpoint: doc["userinfo_endpoint"].as_str()
                    .map(String::from),
            }
        )
    }

    pub fn issuer_uri(&self) -> &str {
        &self.issuer_uri
    }
    pub fn authorization_endpoint(&self) -> &str {
        &self.authorization_endpoint
    }
    pub fn token_endpoint(&self) -> &str {
        &self.token_endpoint
    }
    pub fn jwks_uri(&self) -> &str {
        &self.jwks_uri
    }
    pub fn userinfo_endpoint(&self) -> Option<&String> {
        (&self.userinfo_endpoint).as_ref()
    }
}

// -- Auth Provider's ID Token Claims --
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Claims {
    pub iss: String,
    pub sub: String,
    pub aud: serde_json::Value,
    pub exp: usize,
    pub nonce: Option<String>,
    pub preferred_username: Option<String>,
    pub name: Option<String>,
    pub email: Option<String>,
    pub iat: usize,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct IdTokenClaims {
    pub iss: String,
    pub sub: String,
    pub aud: serde_json::Value,
    pub exp: i64,
    pub iat: usize,
    pub auth_time: Option<i64>,
    pub nonce: Option<String>,
}

// -- Storing Temporary Values Between Requests --
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct AuthFlowCache {
    pub state: String,
    pub nonce: String,
    pub pkce_verifier: String,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct UserSessionData {
    pub claims: Option<Claims>,
    pub access_token: Option<String>,
    pub refresh_token: Option<String>,
}

#[derive(Debug, Clone)]
pub struct OidcClient {
    pub http_client: Client,
    pub oidc_metadata: Arc<OidcProviderMetadata>,
    pub jwk_service: Arc<JwkService>,
}

pub async fn init() -> AppResult<OidcClient> {
    let app_config = config::get();
    let auth_config = app_config.auth_config();

    if auth_config.client_secret() == config::auth::NO_SECRET {
        return Err(AppError::InternalError(anyhow!("client secret not found")));
    }
    //1. Fetch OIDC Provider's Discover Metadata (From /.well-known/openid-configuration)
    let mut issuer_uri = String::from(auth_config.issuer_url());
    issuer_uri.push('/');
    let issuer_uri = Url::parse(issuer_uri.as_str())?;
    tracing::info!("issuer_uri : {}",issuer_uri.as_str());
    let well_known_url = issuer_uri.join(".well-known/openid-configuration")?;
    tracing::info!("well_known_url : {}",well_known_url.as_str());
    let http_client = Client::builder().build()?;
    let discovery_doc: serde_json::Value = http_client.get(well_known_url).send()
        .await?
        .json()
        .await?;
    let oidc_metadata = OidcProviderMetadata::from_discovery_doc(discovery_doc)?;
    tracing::info!("Discovered OIDC Metadata: {:#?}", oidc_metadata);
    let jwk_service = JwkService::new(&oidc_metadata).await?;
    Ok(
        OidcClient {
            http_client,
            oidc_metadata: Arc::new(oidc_metadata),
            jwk_service,
        }
    )
}





